#include <iostream>
#include <fstream>
#include <pthread.h>
#include <map>
#include <stdlib.h>
#include "outputdevice.h"
#include "SyncedTasksQueue.h"
#include "Task.h"

using namespace std;

//======================
//Internal data members
//======================

//Indicates if writer should still be working
bool _isOpen;

//
bool _isInClosing = false;

//the stream we're writing to
ofstream _writingStream;

//Synchorinzed tasks queue
SyncedTasksQueue* _syncTaskQueue;

//a map of tasks with their ID's as keys and pointers to the tasks as values
map<int,Task*>* _tasks;

//a set indicates the task is free to be reallocated
set<int>* _freedID;

//Mutex for locking id allocation
pthread_mutex_t _idMutex;

//Mutex for locking tasks
pthread_mutex_t _tasksMutex;

//Mutex for locking tasks
pthread_mutex_t _freeIDMutex;

//Mutex for locking isOpen
pthread_mutex_t _isOpenMutex;

//error messages
const char* LIB_ERR_MSG = "Output device library error\n";
const char* SYSCALL_ERR_MSG = "system error\n";

//Indicates the library was initialized:
bool _wasInitialized;

//a thread responsible for actually writing to the file:
pthread_t _writerThread;
//a thread responsible for closing the library:
//
pthread_t _closerThread;

int id = 0;


//This function performs the extraction of the task from the synced queue and
//adds it's id to _freeId so it can be reused:
void writeFuncHelper(){
	Task* help = _syncTaskQueue->popTask();
	string data = help->getData();
	int id = help->getID();
	_writingStream << data << endl;
	help->taskWasWritten();
	if(pthread_mutex_lock(&_freeIDMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return;
	}
	_freedID->insert(id);
	if(pthread_mutex_unlock(&_freeIDMutex) !=0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return;
	}
}


//This function constantly queries the synced queue for tasks to write
//and writes them to the file
void* writer(void* data){
	bool flag = true;
	while(flag){
		pthread_mutex_lock(&_isOpenMutex);
		//keep querying while device is open
		if(_isOpen){
			if(!_syncTaskQueue->empty()){
				//write a task to the file:
				writeFuncHelper();
			}
		}
		else{
			flag = false;
		}
		pthread_mutex_unlock(&_isOpenMutex);
	}
	//once the device is closed, we keep going until the queue is empty
	while(!_syncTaskQueue->empty()){
		writeFuncHelper();
	}
	pthread_exit(NULL);
	return NULL;
}

void freeLibResources(){
	_writingStream.close();
	//freeing all allocated memory
	if(pthread_mutex_lock(&_isOpenMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
	}
	_isOpen = false;
	if(pthread_mutex_unlock(&_isOpenMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
	}
	if(pthread_mutex_destroy(&_idMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		exit(0);
	}
	if(pthread_mutex_destroy(&_tasksMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		exit(0);
	}
	if(pthread_mutex_destroy(&_isOpenMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		exit(0);
	}
	map<int,Task*>::iterator taskIter = _tasks->begin();
	while(taskIter != _tasks->end()){
		delete(taskIter->second);
		taskIter++;
	}
	delete(_tasks);
	delete(_freedID);
	delete(_syncTaskQueue);
}

void* closer(void* data){
	void * ignore ;
	if(pthread_join(_writerThread,&ignore) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		exit(0);
	}
	freeLibResources();
	_wasInitialized = false;
	_isInClosing = false;
	pthread_exit(NULL);
	return NULL;
}

//This function returns the next available id through reuse
int getTaskNextId(){
	int id = 0;
	pthread_mutex_lock(&_freeIDMutex);
	if(_freedID->size() == 0){
		if(pthread_mutex_lock(&_tasksMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		}
		//if all of the 'old' ids are in use, the subsequent id is returned:
		if(_tasks->size() != 0){
			id =  (--(_tasks->end()))->first+1;
		}
		if(pthread_mutex_unlock(&_tasksMutex) != 0 ){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);

		}
		if(pthread_mutex_unlock(&_freeIDMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		}
		return id;
	}
	//if there's a free id return it and delete it from the free ids set:
	else{
		id = *(_freedID->begin());
		_freedID->erase(id);
		if(pthread_mutex_unlock(&_freeIDMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		}
		return id;
	}
}



//=========================================================================
//							External interface
//=========================================================================


int initdevice(char *filename)
{
	if(_isInClosing){
		fprintf(stderr,"%s", LIB_ERR_MSG);
		return -1;
	}
	_tasks = new map<int,Task*>();
	_freedID = new set<int>();
	_writingStream.open(filename, ios::app);
	if(!_writingStream.is_open())
	{
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return -2;
	}
	if(pthread_mutex_init(&_idMutex,NULL) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return -1;
	}
	if(pthread_mutex_init(&_isOpenMutex,NULL) !=0 ){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return -1;
	}
	if(pthread_mutex_init(&_tasksMutex,NULL) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return -1;
	}
	_syncTaskQueue = new SyncedTasksQueue();
	if(pthread_mutex_lock(&_isOpenMutex) !=0 ){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return -1;
	}
	_isOpen = true;
	if(pthread_mutex_unlock(&_isOpenMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return -1;
	}
	if (pthread_create(&_writerThread,NULL,writer,0) < 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return -1;
	}
	_wasInitialized = true;
	return 0;
}

int write2device(char *buffer, int length){
	//copy the buffer
	string tempStr = string(buffer,length);
	if(!_isInClosing && _wasInitialized && _isOpen){
		if(pthread_mutex_lock(&_idMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			return -1;
		}
		int id = getTaskNextId();
		if(pthread_mutex_unlock(&_idMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			return -1;
		}
		if(pthread_mutex_lock(&_tasksMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			return -1;
		}
//		map<int,Task*>::iterator taskIter = _tasks->find(id);
//		if(taskIter != _tasks->end()){
//			cout<<"hi"<<endl;
//			delete(taskIter->second);
//		}
		Task* tempTask = new Task(id,tempStr);
		_tasks->insert( pair<int,Task*>(id,tempTask));
		if(pthread_mutex_unlock(&_tasksMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			return -1;
		}
		_syncTaskQueue->pushTask(tempTask);
		return id;
	}
	//else there was an attempt to write to the device when the device wasn't initialized
	//or already closed:
	else{
		fprintf(stderr,"%s", LIB_ERR_MSG);
		return -1;
	}
}


void flush2device(int task_id){
	if(!_wasInitialized){
		fprintf(stderr,"%s", LIB_ERR_MSG);
		freeLibResources();
		exit(0);
	}
	if(pthread_mutex_lock(&_tasksMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		freeLibResources();
		exit(0);
	}
	map<int,Task*>::iterator taskIter = _tasks->find(task_id);
	if( taskIter == _tasks->end()){
		fprintf(stderr,"%s", LIB_ERR_MSG);
		pthread_mutex_unlock(&_tasksMutex);
		if(_isInClosing){
			void* ignore;
			pthread_join(_closerThread,&ignore);
		}
		else{
			freeLibResources();
		}
		exit(0);
	}
	if(pthread_mutex_unlock(&_tasksMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		freeLibResources();
		exit(0);
	}
	if(pthread_mutex_lock(&_isOpenMutex) != 0 ){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		freeLibResources();
		exit(0);
	}
	//if the task was already written to the file return:
	if(taskIter->second->hasBeenWritten()){
		if(pthread_mutex_unlock(&_isOpenMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			freeLibResources();
			exit(0);
		}
		return;
	}
	//else wait for the task to be written and return
	else
	{
		if(pthread_cond_wait(taskIter->second->getTaskCondVar(),&_isOpenMutex) !=0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			freeLibResources();
			exit(0);
		}
	}
	if(pthread_mutex_unlock(&_isOpenMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		freeLibResources();
		exit(0);
	}
	return;
}


int wasItWritten(int task_id){
	int result;
	if(!_wasInitialized){
		fprintf(stderr,"%s", LIB_ERR_MSG);
		return -1;
	}
	if(pthread_mutex_lock(&_tasksMutex) != 0){
		fprintf(stderr,"%s", SYSCALL_ERR_MSG);
		return -1;
	}
	map<int,Task*>::iterator taskIter = _tasks->find(task_id);
	if(taskIter == _tasks->end()){
		if(pthread_mutex_unlock(&_tasksMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			return -1;
		}
		fprintf(stderr,"%s", LIB_ERR_MSG);
		return -2;
	}
	else{
		if(taskIter->second->hasBeenWritten()){
			result = 0;
		}
		else{
			result = 1;
		}
		if(pthread_mutex_unlock(&_tasksMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			return -1;
		}
		return result;
	}
}

void closedevice(){
	if(_wasInitialized){
		_isInClosing = true;
		if(pthread_mutex_lock(&_isOpenMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			freeLibResources();
			exit(0);
		}
		_isOpen = false;
		if(pthread_mutex_unlock(&_isOpenMutex) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			freeLibResources();
			exit(0);
		}
		if (pthread_create(&_closerThread,NULL,closer,0) != 0){
			fprintf(stderr,"%s", SYSCALL_ERR_MSG);
			return;
		}
		return;
	}
	else
	{
		return;
	}
}

